use rust_decimal::{Decimal, MathematicalOps};

#[derive(Debug, PartialEq, Clone)]
pub enum Node {
    Add(Box<Node>, Box<Node>),
    Subtract(Box<Node>, Box<Node>),
    Multiply(Box<Node>, Box<Node>),
    Divide(Box<Node>, Box<Node>),
    Power(Box<Node>, Box<Node>),
    Negative(Box<Node>),
    Number(Decimal),
}

impl Node {
    pub fn eval(&self) -> Decimal {
        use Node::*;
        match self {
            Add(left, right) => left.eval() + right.eval(),
            Subtract(left, right) => left.eval() - right.eval(),
            Multiply(left, right) => left.eval() * right.eval(),
            Divide(left, right) => left.eval() / right.eval(),
            Power(left, right) => left.eval().powd(right.eval()),
            Negative(inner) => -inner.eval(),
            Number(value) => *value,
        }
    }
}

#[cfg(test)]
mod tests {
    use crate::calc::ast::Node;
    use rust_decimal::dec;

    #[test]
    fn test_eval_add() {
        let node = Node::Add(
            Box::new(Node::Number(dec!(3))),
            Box::new(Node::Number(dec!(5))),
        );
        assert_eq!(node.eval(), dec!(8));
    }

    #[test]
    fn test_eval_subtract() {
        let node = Node::Subtract(
            Box::new(Node::Number(dec!(3))),
            Box::new(Node::Number(dec!(5))),
        );
        assert_eq!(node.eval(), dec!(-2));
    }
    #[test]
    fn test_eval_multiply() {
        let node = Node::Multiply(
            Box::new(Node::Number(dec!(3))),
            Box::new(Node::Number(dec!(5))),
        );
        assert_eq!(node.eval(), dec!(15));
    }
    #[test]
    fn test_eval_divide() {
        let node = Node::Divide(
            Box::new(Node::Number(dec!(15))),
            Box::new(Node::Number(dec!(5))),
        );
        assert_eq!(node.eval(), dec!(3));
    }
    #[test]
    fn test_eval_power() {
        let node = Node::Power(
            Box::new(Node::Number(dec!(2))),
            Box::new(Node::Number(dec!(3))),
        );
        assert_eq!(node.eval(), dec!(8));
    }
    #[test]
    fn test_eval_negative() {
        let node = Node::Negative(Box::new(Node::Number(dec!(3))));
        assert_eq!(node.eval(), dec!(-3));
    }

    #[test]
    fn test_eval_number() {
        let node = Node::Number(dec!(42));
        assert_eq!(node.eval(), dec!(42));
    }
}
